package org.xukai.remoting.http;

import org.apache.http.client.HttpClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.InitializingBean;
import org.xukai.common.thread.DaemonThreadFactory;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author xukai
 * @desc
 * @date 2017-04-26-19:08
 */
public class HttpClientManagerImpl implements InitializingBean {



        Log log = LogFactory.getLog(HttpClientManagerImpl.class);
        private PoolingHttpClientConnectionManager connectionManager;
        private HttpClient client;
        private final long heartbeatPeriod = 10000L;
        private final long initialDelay = 10000L;
        private final TimeUnit unit;
        public int maxTotalConnections;
        public int maxRouteConnections;
        public int connectTimeout;
        public int readTimeout;

        public HttpClientManagerImpl() {
            this.unit = TimeUnit.MILLISECONDS;
            this.maxTotalConnections = 400;
            this.maxRouteConnections = 200;
            this.connectTimeout = 10000;
            this.readTimeout = 20000;
        }

        public void setMaxTotalConnections(int maxTotalConnections) {
            this.maxTotalConnections = maxTotalConnections;
        }

        public void setMaxRouteConnections(int maxRouteConnections) {
            this.maxRouteConnections = maxRouteConnections;
        }

        public void setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
        }

        public void setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
        }

        public void afterPropertiesSet() throws Exception {
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
            schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
            ConnectionSocketFactory plainsf = PlainConnectionSocketFactory
                    .getSocketFactory();
            LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainsf)
                    .register("https", sslsf).build();
            this.connectionManager = new PoolingHttpClientConnectionManager(registry);
            this.connectionManager.setMaxTotal(this.maxTotalConnections);
            this.connectionManager.setDefaultMaxPerRoute(this.maxRouteConnections);
            // 请求重试处理
            HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
                public boolean retryRequest(IOException exception,
                                            int executionCount, HttpContext context) {
                    if (executionCount >= 5) {// 如果已经重试了5次，就放弃
                        return false;
                    }
                    if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                        return true;
                    }
                    if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                        return false;
                    }
                    if (exception instanceof InterruptedIOException) {// 超时
                        return false;
                    }
                    if (exception instanceof UnknownHostException) {// 目标服务器不可达
                        return false;
                    }
                    if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                        return false;
                    }
                    if (exception instanceof SSLException) {// SSL握手异常
                        return false;
                    }

                    HttpClientContext clientContext = HttpClientContext
                            .adapt(context);
                    HttpRequest request = clientContext.getRequest();
                    // 如果请求是幂等的，就再次尝试
                    if (!(request instanceof HttpEntityEnclosingRequest)) {
                        return true;
                    }
                    return false;
                }
            };
            client = HttpClients.custom().setConnectionManager(connectionManager).setRetryHandler(httpRequestRetryHandler).build();
            this.client.getParams().setParameter("http.connection.timeout", Integer.valueOf(this.connectTimeout));
            this.client.getParams().setParameter("http.socket.timeout", Integer.valueOf(this.readTimeout));

            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory("httpClient-con-monitor"));
            scheduler.scheduleAtFixedRate(new HttpClientManagerImpl.IdleConnectionMonitor(this.connectionManager), 10000L, 10000L, this.unit);
        }

        public HttpClient getHttpClient() {
            return this.client;
        }

        private final class IdleConnectionMonitor implements Runnable {
            PoolingHttpClientConnectionManager connectionManager;

            public IdleConnectionMonitor(PoolingHttpClientConnectionManager connectionManager) {
                this.connectionManager = connectionManager;
            }

            public void run() {
                if(HttpClientManagerImpl.this.log.isDebugEnabled()) {
                    HttpClientManagerImpl.this.log.debug("release start connect count:=" + this.connectionManager.getTotalStats().getAvailable());
                }

                this.connectionManager.closeExpiredConnections();
                this.connectionManager.closeIdleConnections((long)(HttpClientManagerImpl.this.readTimeout * 2), TimeUnit.MILLISECONDS);
                if(HttpClientManagerImpl.this.log.isDebugEnabled()) {
                    HttpClientManagerImpl.this.log.debug("release end connect count:=" + this.connectionManager.getTotalStats().getAvailable());
                }

            }
        }


}
